package local.blueheart.data.springdatademo.util.mht;

import local.blueheart.data.springdatademo.util.StringUtil;
import local.blueheart.data.springdatademo.util.URLUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.htmlparser.Parser;
import org.htmlparser.Tag;
import org.htmlparser.filters.HasAttributeFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.lexer.Lexer;
import org.htmlparser.lexer.Page;
import org.htmlparser.util.DefaultParserFeedback;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.util.SimpleNodeIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.activation.DataSource;
import javax.activation.MimetypesFileTypeMap;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * Domino文档页转换为MHT文件
 */
public class MHTHelper {

    private static Logger LOGGER = LoggerFactory.getLogger(MHTHelper.class);

    public static final String strEncoding = "gb2312";

    /**
     * 登陆地址
     */
    private String loginUrl;

    /**
     * 登陆用户名
     */
    private String userName;

    /**
     * 登陆密码
     */
    private String passWord;

    /**
     * 是否独立保存附件
     */
    private boolean isIndividualAttachment = true;

    /**
     * 下载HTML及文件工具类
     */
    private JQuery jQuery;


    ThreadLocal<MHTHelperInfo> helperInfoLocal = new ThreadLocal<>();


    public MHTHelper(String loginUrl, String userName, String passWord) {
        this.loginUrl = loginUrl;
        this.userName = userName;
        this.passWord = passWord;
        jQuery = new JQuery();
        jQuery.login(loginUrl, userName, passWord);
    }


    /**
     * 打包指定的URL为一个mht文件
     *
     * @param requestUrl
     * @param mhtFileLocalPath
     * @param encoding
     * @param mhtFileName
     */
    public void packArchivedMht(String requestUrl,String dbPath, String mhtFileLocalPath, String encoding, String mhtFileName, boolean isIndividualAttachment) throws MHTTransferException {
        if (StringUtil.isBlank(mhtFileLocalPath) || StringUtil.isBlank(requestUrl)) {
            return;
        }
        try {
            String htmlText = jQuery.getHtml(requestUrl, encoding);
            //保存本地变量
            MHTHelperInfo helperInfo = new MHTHelperInfo();
            helperInfo.setHtmlText(htmlText);
            helperInfo.setBaseWebURL(getBaseUrl(requestUrl));
            helperInfo.setDbPath(dbPath);
            helperInfo.setLocalPath(mhtFileLocalPath);
            helperInfo.setFileName(mhtFileName);
            helperInfo.setIndividualAttachment(isIndividualAttachment);
            helperInfoLocal.set(helperInfo);
            MimeMessage mimeMessage = createMHTArchive(htmlText, encoding);
            if (mimeMessage != null) {
                FileUtils.forceMkdir(new File(mhtFileLocalPath));
                FileOutputStream out = new FileOutputStream(new File(mhtFileLocalPath + "/" + mhtFileName));
                mimeMessage.writeTo(out);
                out.close();
            }
        } catch (IOException e) {
            LOGGER.error("IO错误", e);
            throw new MHTTransferException(e);
        } catch (MessagingException e) {
            LOGGER.error("message消息创建错误", e);
            throw new MHTTransferException(e);
        } catch (ParserException e) {
            LOGGER.error("转换错语", e);
            throw new MHTTransferException(e);
        } catch (Exception e) {
            LOGGER.error("其它错误", e);
            throw new MHTTransferException(e);
        }
    }

    /**
     * 获取基本地址
     *
     * @param docURL
     * @return
     * @throws MalformedURLException
     * @throws URISyntaxException
     */
    private URL getBaseUrl(String docURL) throws MalformedURLException, URISyntaxException {
        URL url = new URL(docURL);
        URI uri = new URIBuilder().setScheme(url.getProtocol()).setHost(url.getHost()).setPort(url.getPort()).build();
        return uri.toURL();

    }

    private Set<MHTAttachmentInfo> extractAttachmentInfo(NodeList nodes) throws MalformedURLException, UnsupportedEncodingException {
        Set<MHTAttachmentInfo> allResourcesSet = new HashSet<>();
        allResourcesSet.addAll(extractAllAttachmentNodes(nodes));
        allResourcesSet.addAll(extractAllBackgroundNodes(nodes));
        allResourcesSet.addAll(extractAllCssNodes(nodes));
        allResourcesSet.addAll(extractAllImageNodes(nodes));
        allResourcesSet.addAll(extractAllScriptNodes(nodes));
        return allResourcesSet;
    }


    /**
     * 进行URL替换
     * @param attachmentInfos
     */
    private void replaceHtmlTextLink(Set<MHTAttachmentInfo> attachmentInfos) {
        attachmentInfos.forEach(mhtAttachmentInfo -> {
            helperInfoLocal.get().setHtmlText(StringUtil.replace(helperInfoLocal.get().getHtmlText(), mhtAttachmentInfo.getInnerUrl(), mhtAttachmentInfo.getAbsoluteUrl()));
        });
    }

    /**
     * 创建MimeMessage消息
     * @param htmlText
     * @param encoding
     * @return
     * @throws Exception
     */
    private MimeMessage createMHTArchive(String htmlText, String encoding) throws Exception {
        Parser parser = createParser(htmlText);
        parser.setEncoding(encoding);
        NodeList nodes = parser.parse(null);
        Set<MHTAttachmentInfo> attachmentInfos = extractAttachmentInfo(nodes);
        //修改文本进行替换
        replaceHtmlTextLink(attachmentInfos);
        MimeMessageBuilder mimeMessageBuilder = MimeMessageBuilder.defaultInstance();
        Map<String, String> htmlHeader = new HashMap<>();
        htmlHeader.put("Content-Type", "text/html;charset=gb2312");
        mimeMessageBuilder.setHeader("X-Mailer", "Code Manager .SWT").setFrom("admin@zoneland.net").setSubject("", encoding).setMimeMultiPart("related").setHTMLBodyPart(helperInfoLocal.get().getHtmlText(), encoding, htmlHeader);
        for(MHTAttachmentInfo attachmentInfo:attachmentInfos){
            LOGGER.info(attachmentInfo.toString());
            if(attachmentInfo.getAttachmentType().equals("a")){
                extractAttachments(attachmentInfo);
            }else {
                DataSource dataSource = extractDataSource(attachmentInfo);
                mimeMessageBuilder.addBodyPart(attachmentInfo.getAbsoluteUrl(),encoding,dataSource);
            }
        }
        return mimeMessageBuilder.build();


    }

    /**
     * 下载文件到子目录
     * @param mhtAttachmentInfo
     * @throws IOException
     */
    private void extractAttachments(MHTAttachmentInfo mhtAttachmentInfo) throws IOException {
        String temp = StringUtil.replace(mhtAttachmentInfo.getAbsoluteUrl(),"./","");
        String relativePath = temp.split("/")[0];
        String fileName = temp.split("/")[1];
        String localBasePath = helperInfoLocal.get().getLocalPath();
        String filePath = localBasePath+"/"+relativePath;
        FileUtils.forceMkdir(new File(filePath));
        String localPath = filePath+"/"+fileName;
        jQuery.downFile(mhtAttachmentInfo.getDownloadUrl(),new File(localPath));


    }

    /**
     * 加入相应的资源文件到打包文件里
     * @param mhtAttachmentInfo
     * @return
     * @throws IOException
     */
    private DataSource extractDataSource(MHTAttachmentInfo mhtAttachmentInfo) throws IOException {
        return new AttachmentDataSource(mhtAttachmentInfo.getAbsoluteUrl(),mhtAttachmentInfo.getAttachmentType(),jQuery);
    }


    /**
     * 分离得到A标记相关资源链接
     * @param nodes
     * @return
     */
    private Set<MHTAttachmentInfo> extractAllAttachmentNodes(NodeList nodes) throws MalformedURLException, UnsupportedEncodingException {
        Set<MHTAttachmentInfo> aInfo = new HashSet<>();
        NodeList filtered = nodes.extractAllNodesThatMatch(new TagNameFilter("a"),true);
        SimpleNodeIterator tags = filtered.elements();
        while (tags.hasMoreNodes()){
            Tag tag = (Tag) tags.nextNode();
            //LOGGER.info("tag ={},onclick={}",tag.getAttribute("onclick"));
            String href = tag.getAttribute("href");
            String onclick = tag.getAttribute("onclick");
            if(StringUtil.isNotBlank(href) && StringUtil.isNotBlank(onclick)){
                //原始地址
                String innerURL = href;
                //资源下载地址
                URL baseURL = helperInfoLocal.get().getBaseWebURL();
                String dbPath = helperInfoLocal.get().getDbPath();
                String downloadUrl = makeAbsoluteURL(baseURL,dbPath+"/"+innerURL);
                String fileFullName = URLUtil.getLastPath(downloadUrl);
                String subDir = URLUtil.getFileName(helperInfoLocal.get().getFileName());
                //替换后的地址
                String absoluteURL = "./"+subDir+"/"+fileFullName;
                String encodFileFullName = URLUtil.encode(fileFullName);
                downloadUrl = StringUtil.replaceAll(downloadUrl,fileFullName,encodFileFullName);
                MHTAttachmentInfo info = new MHTAttachmentInfo(innerURL,absoluteURL,downloadUrl,"a");
                aInfo.add(info);
                //替换资源链接
                tag.setAttribute("href", absoluteURL);
            }
        }
        return aInfo;
    }

    /**
     * 分离CSS文件
     * @param nodes
     * @return
     */
    private  Set<MHTAttachmentInfo> extractAllCssNodes(NodeList nodes) {
        Set<MHTAttachmentInfo> aInfo = new HashSet<>();
        NodeList filtered = nodes.extractAllNodesThatMatch(new TagNameFilter("link"), true);
        for (int i = 0; i < filtered.size(); i++) {
            Tag tag = (Tag) filtered.elementAt(i);
            String type = (tag.getAttribute("type"));
            String rel = (tag.getAttribute("rel"));
            String href = tag.getAttribute("href");

            boolean isCssFile = false;
            if (StringUtils.isNotBlank(rel)) {
                isCssFile = rel.indexOf("stylesheet") != -1;
            } else if (StringUtils.isNotBlank(type)) {
                isCssFile |= type.indexOf("text/css") != -1;
            }
            // Handle external css file's url
            if (isCssFile && StringUtils.isNotBlank(href)) {
                String innerURL = href;
                String absoluteURL = makeAbsoluteURL(helperInfoLocal.get().getBaseWebURL(), innerURL);
                String downloadURL = absoluteURL;
                MHTAttachmentInfo info = new MHTAttachmentInfo(innerURL,absoluteURL,downloadURL,"css");
                aInfo.add(info);
                tag.setAttribute("href", absoluteURL);
            }
        }
        return aInfo;
    }


    /**
     * 分离background image图片
     * @param nodes
     * @return
     */
    private Set<MHTAttachmentInfo> extractAllBackgroundNodes(NodeList nodes) {
        Set<MHTAttachmentInfo> aInfo = new HashSet<>();
        NodeList filtered = nodes.extractAllNodesThatMatch(new HasAttributeFilter("background"), true);
        for (int i = 0; i < filtered.size(); i++) {
            Tag tag = (Tag) filtered.elementAt(i);
            String src = tag.getAttribute("background");
            if (src != null && src.length() > 0) {
                String innerURL = src;
                String absoluteURL = makeAbsoluteURL(helperInfoLocal.get().getBaseWebURL(), innerURL);
                String downloadURL = absoluteURL;
                MHTAttachmentInfo info = new MHTAttachmentInfo(innerURL,absoluteURL,downloadURL,"image");
                aInfo.add(info);
                tag.setAttribute("background", absoluteURL);
            }
        }

        return aInfo;
    }

    /**
     * 分离 image图片
     * @param nodes
     * @return
     */
    private Set<MHTAttachmentInfo> extractAllImageNodes(NodeList nodes) {
        Set<MHTAttachmentInfo> aInfo = new HashSet<>();
        NodeList filtered = nodes.extractAllNodesThatMatch(new TagNameFilter("IMG"), true);
        for (int i = 0; i < filtered.size(); i++) {
            Tag tag = (Tag) filtered.elementAt(i);
            String src = tag.getAttribute("src");
            // Handle external css file's url
            if (src != null && src.length() > 0) {
                String innerURL = src;
                String absoluteURL = makeAbsoluteURL(helperInfoLocal.get().getBaseWebURL(), innerURL);
                String downloadURL = absoluteURL;
                MHTAttachmentInfo info = new MHTAttachmentInfo(innerURL,absoluteURL,downloadURL,"image");
                aInfo.add(info);
                tag.setAttribute("src", absoluteURL);
            }
        }
        return aInfo;
    }


    /**
     * 拆离script
     * @param nodes
     * @return
     */
    private Set<MHTAttachmentInfo> extractAllScriptNodes(NodeList nodes) {
        Set<MHTAttachmentInfo> aInfo = new HashSet<>();
        NodeList filtered = nodes.extractAllNodesThatMatch(new TagNameFilter("script"), true);
        for (int i = 0; i < filtered.size(); i++) {
            Tag tag = (Tag) filtered.elementAt(i);
            String src = tag.getAttribute("src");
            if (StringUtils.isNotBlank(src)) {
                String innerURL = src;
                String absoluteURL = makeAbsoluteURL(helperInfoLocal.get().getBaseWebURL(), innerURL);
                String downloadURL = absoluteURL;
                MHTAttachmentInfo info = new MHTAttachmentInfo(innerURL,absoluteURL,downloadURL,"script");
                aInfo.add(info);
                tag.setAttribute("src", absoluteURL);
            }
        }
        return aInfo;
    }


    private Parser createParser(String inputHTML) {
        Lexer mLexer = new Lexer(new Page(inputHTML));
        return new Parser(mLexer, new DefaultParserFeedback(DefaultParserFeedback.QUIET));
    }

    private String makeRelativeURL(URL strWeb,String innerURL,String mhtFileName){
        URL linkUri = null;
        try {
            linkUri = new URL(strWeb, innerURL);
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        }
        return "";

    }

    private String makeAbsoluteURL(URL strWeb, String innerURL) {
        //绝对路径直接返回
        if (innerURL != null && innerURL.toLowerCase().indexOf("http") == 0) {
            return innerURL;
        }

        URL linkUri = null;
        try {
            linkUri = new URL(strWeb, innerURL);
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        }

        String absURL = linkUri.toString();
        absURL = StringUtil.replace(absURL, "../", "");
        absURL = StringUtil.replace(absURL, "./", "");
        return absURL;
    }


    public static void main(String[] args)  {
        try {
            MHTHelper mhtHelper = new MHTHelper("devemis.jx.chinamobile.com", "admin", "1");
            String docURL = "http://devemis.jx.chinamobile.com/lib/fsarchive/fsarchivestore_B5T9QH.nsf/0/C7CF6C8D7FBCCB1C4825835400303DB8?openDocument";
            String dbPath = "lib/fsarchive/fsarchivestore_B5T9QH.nsf";
            String docSavePath = "/Users/liuzhenxing/temp/dominotransfer/lib/fsarchive/fsarchivestore_B5T9QH/nsf";
            String encoding = "gb2312";
            mhtHelper.packArchivedMht(docURL, dbPath, docSavePath, encoding, "C7CF6C8D7FBCCB1C4825835400303DB8.mht", true);
        } catch (MHTTransferException e) {
            e.printStackTrace();
        }
    }

}

class AttachmentDataSource implements DataSource {
    private MimetypesFileTypeMap map = new MimetypesFileTypeMap();
    private String strUrl;
    private String strType;
    private byte[] dataSize = null;

    /**
     * This is some content type maps.
     */
    private Map<String, String> normalMap = new HashMap<>();

    {
        // Initiate normal mime type map
        // Images
        normalMap.put("image", "image/jpeg");
        normalMap.put("text", "text/plain");
        normalMap.put("script", "text/javascript");
        normalMap.put("css", "text/css");
    }

    public AttachmentDataSource(String strUrl, String strType, JQuery jquery) throws IOException {
        this.strType = strType;
        this.strUrl = strUrl;
        strUrl = strUrl.trim();
        strUrl = StringUtil.replaceAll(strUrl," ","%20");
        dataSize = jquery.downBinaryFileByJquery(strUrl);
    }

    /**
     * Returns the content type.
     */
    public String getContentType() {
        return getMimeType(getName());
    }

    public String getName() {
        char separator = File.separatorChar;
        if (strUrl.lastIndexOf(separator) >= 0)
            return strUrl.substring(strUrl.lastIndexOf(separator) + 1);
        return strUrl;
    }

    public InputStream getInputStream() {
        if (dataSize == null)
            dataSize = new byte[0];
        return new ByteArrayInputStream(dataSize);
    }

    public OutputStream getOutputStream() {
        return new java.io.ByteArrayOutputStream();
    }

    private String getMimeType(String fileName) {
        String type = (String) normalMap.get(strType);
        if (type == null) {
            try {
                type = map.getContentType(fileName);
            } catch (Exception e) {

            }
            if (type == null) {
                type = "application/octet-stream";
            }
        }
        return type;
    }

}

